//=============================================================================
//     _   _                               ____                _  _          
//    | | | |  __ _  _ __   _ __   _   _  / ___|  _ __   _ __ (_)| |_   ___ 
//    | |_| | / _` || '_ \ | '_ \ | | | | \___ \ | '_ \ | '__|| || __| / _ \
//    |  _  || (_| || |_) || |_) || |_| |  ___) || |_) || |   | || |_ |  __/
//    |_| |_| \__,_|| .__/ | .__/  \__, | |____/ | .__/ |_|   |_| \__| \___|
//                  |_|    |_|     |___/         |_|                         
//
//                     HappySprite - We make sprites happy
//
// Copyright (c) 2007 by Tank Monkey Games
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//=============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

namespace HappySprite.Simulation
{
    public class World
    {
        internal const bool AccumulateImpulses = false;
        internal const bool SplitImpulses = false;
        internal const bool PositionCorrection = false;
        internal const bool WarmStarting = false;
        internal const int Iterations = 10;

        static CollisionEventArgs e = new CollisionEventArgs();

        Vector2 gravity;
        bool hasGravity;
        Broadphase broadphase;
        Narrowphase narrowphase;
        bool performingUpdate;

        List<Body> items = new List<Body>();
        List<Body> pendingAdds = new List<Body>();
        List<Body> pendingRemoves = new List<Body>();

        ObjectPool<Arbiter> arbiterPool = new ObjectPool<Arbiter>();
        Dictionary<Arbiter, Arbiter> arbitersOne = new Dictionary<Arbiter, Arbiter>();
        Dictionary<Arbiter, Arbiter> arbitersTwo = new Dictionary<Arbiter, Arbiter>();
        Dictionary<Arbiter, Arbiter> arbiters;
        Dictionary<Arbiter, Arbiter> oldArbiters;
        bool usingArbitersOne;

        public event GenericEventHandler<CollisionEventArgs> Collision;

        public World()
            : this(new SimpleBroadphase(), new DefaultNarrowphase())
        {
        }

        public World(Broadphase broadphase, Narrowphase narrowphase)
        {
            this.broadphase = broadphase;
            this.narrowphase = narrowphase;
            broadphase.SetPotentialCollisionCallback(PotentialCollisionHandler);
        }

        public Vector2 Gravity
        {
            get 
            { 
                return gravity; 
            }
            set 
            { 
                gravity = value;
                hasGravity = gravity != Vector2.Zero;
            }
        }

        public IEnumerable<Body> Items
        {
            get { return items; }
        }

        public void Add(Body item)
        {
            if (performingUpdate)
            {
                pendingAdds.Add(item);
                return;
            }

            if (item.Owner == this)
                return;

            if (item.Owner != null)
                item.Owner.Remove(item);

            item.AssignOwner(this);
            items.Add(item);
            broadphase.Add(item);
        }

        public void Remove(Body item)
        {
            if (performingUpdate)
            {
                pendingRemoves.Add(item);
                return;
            }

            if (item.Owner != this)
                return;

            item.AssignOwner(null);
            items.Remove(item);
            broadphase.Remove(item);
        }

        public void Update(GameTime gameTime)
        {
            Update((float)gameTime.ElapsedGameTime.TotalSeconds);
        }

        public void Update(float timeStep)
        {
            // Swap arbiter collections.
            usingArbitersOne = !usingArbitersOne;
            arbiters = usingArbitersOne ? arbitersOne : arbitersTwo;
            oldArbiters = usingArbitersOne ? arbitersTwo : arbitersOne;

            performingUpdate = true;

            // Collision detection and handling.
            broadphase.DetectPotentialCollisions();

            // Free old arbiters
            foreach (Arbiter arbiter in oldArbiters.Values)
                arbiterPool.Recycle(arbiter);

            oldArbiters.Clear();

            // Apply gravity.
            if (hasGravity)
            {
                foreach (Body item in items)
                    item.AddAcceleration(gravity);
            }

            // Integrate forces.
            foreach (Body item in items)
                item.IntegrateForces(timeStep);

            // Perform pre-steps.
            float inverseTimeStep = timeStep > 0f ? 1f / timeStep : 0f;

            foreach (Arbiter arbiter in arbiters.Values)
                arbiter.PreStep(inverseTimeStep);

            // Perform iterations
            for (int i = 0; i < Iterations; i++)
            {
                foreach (Arbiter arbiter in arbiters.Values)
                    arbiter.ApplyImpulse();
            }

            // Integrate velocities.
            foreach (Body item in items)
                item.IntegrateVelocities(timeStep);

            // Check world limits.
            foreach (Body item in items)
                item.PerformWorldLimitsCheck();

            performingUpdate = false;

            // Apply pending additions and removes.
            foreach (Body item in pendingRemoves)
                Remove(item);

            foreach (Body item in pendingAdds)
                Add(item);

            pendingRemoves.Clear();
            pendingAdds.Clear();
        }

        internal void ActualCollisionHandler(CollisionEventArgs e)
        {
            Body u = e.This;
            Body v = e.Other;

            // Perform a physics collision response
            if (PhysicsCollisionCheck(u, v))
                PhysicsCollisionHandler(e);

            // Run global event handler
            if (Collision != null)
                Collision(e);

            // Run body event handlers
            u.HandleCollision(e);
            e.Reverse();
            v.HandleCollision(e);
        }

        internal void PhysicsCollisionHandler(CollisionEventArgs e)
        {
            // If both bodies have infinite mass, forget about it
            if (e.This.InverseMass == 0f && e.Other.InverseMass == 0f)
                return;

            Arbiter arbiter = arbiterPool.Create();
            arbiter.Initialize(e);

            Arbiter old;
            if (oldArbiters.TryGetValue(arbiter, out old))
                arbiter.Merge(ref old);

            arbiters.Add(arbiter, arbiter);
        }

        internal void PotentialCollisionHandler(Body u, Body v)
        {
            // Initialize the CollisionEventArgs singleton
            e.Reset();
            e.CollisionDetails = Body.MinimumRequiredCollisionDetails(u, v);
            e.This = u;
            e.Other = v;

            // Narrowphase collision detection
            if (narrowphase.DetectCollision(e))
                ActualCollisionHandler(e);
        }

        internal void OnTransformChanged(Body body)
        {
            broadphase.Update(body);
        }

        internal static bool PhysicsCollisionCheck(Body u, Body v)
        {
            return u.CollisionResponse == CollisionResponse.Physics
                || v.CollisionResponse == CollisionResponse.Physics;
        }
    }
}
